/*
 *  popselect - v0.1.14
 *  Replaces traditional <select> with a options from popover
 *  http://jquer.in/popselect
 *
 *  Made by Jay Kanakiya
 *  Under MIT License
 */
;(function ($, window, document, undefined) {

    'use strict';

    // Create the defaults once
    var pluginName = 'popSelect';
    var defaults = {
        position: 'top',
        showTitle: true,
        autoIncrease: true,
        title: 'Select Multiple Options',
        debug: false,
        maxAllowed: 0,
        placeholderText: 'Click to Add Values',
        autofocus: false
    };

    var classNames = {
        tag: 'tag',
        arrow: 'arrow',
        selectWrapper: 'popover-select-wrapper',
        tagWrapper: 'popover-tag-wrapper',
        popoverSelect: 'popover-select',
        popoverBody: 'popover-select-body',
        selectTextarea: 'popover-select-textarea',
        selectTags: 'popover-select-tags',
        popoverClose: 'popSelect-close',
        selectList: 'popover-select-list',
        popoverDisabled: 'disabled',
        placeholder: 'placeholder',
        placeholderInput: 'placeholder input',
        placeholderText: 'placeholder-text',
        selectTitle: 'popover-select-title',
        top: 'top'
    };

    var logs = {
        popoverGenerated: 'PopSelect Code Generated',
        closeClicked: 'Close button clicked',
        noElem: 'No element to be removed',
        unSupported: 'Not Supported',
        posChanged: 'Position changed'
    };

    var constants = {
        option: 'option',
        blur: 'blur',
        click: 'click',
        mousedown: 'mousedown',
        li: 'li',
        attrVal: 'data-value',
        attrText: 'data-text',
        body: 'BODY'
    };

    // The actual plugin constructor
    function Plugin(element, options) {
        this.element = element;
        // jQuery has an extend method which merges the contents of two or
        // more objects, storing the result in the first object. The first object
        // is generally empty as we don't want to alter the default options for
        // future instances of the plugin
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    // Avoid Plugin.prototype conflicts
    $.extend(Plugin.prototype, {
        init: function () {
            var $this = this;
            this.$elem = $(this.element);

            // Get all the options in an array
            this.$options = this.$elem.children(constants.option).map(function (i, option) {
                return {
                    val: $(option).val(),
                    text: $(option).text(),
                    selected: $(option).attr('selected')
                };
            });

            // Wrap the whole input box in your own popover
            this.$elem.wrap(template(createEmptyDiv(), {
                wrapper: classNames.selectWrapper
            }));

            var elemPos = this.getPosition(this.$elem);
            this.elemPos = elemPos;

            // Also Add the required css Properties
            this.$elem
                .parent(addDot(classNames.selectWrapper))
                .css({width: this.settings.width || elemPos.width, height: elemPos.height});

            // Append the popover to $elem
            var popUpCode = this.generatePopover(this.$options);
            $this.log(logs.popoverGenerated, popUpCode);
            this.$elem.after(popUpCode);

            // Assign the $popover to the new $elem
            this.$popover = this.$elem.next(addDot(classNames.popoverSelect));
            this.$popover.css({top: 0, left: 0});

            // Append Tagging System to it
            this.$elem.after(createTaggingStr(this.settings.placeholderText, this.$options));

            // Get the Tag Wrapper for later use
            this.$tagWrapper = this.$elem.next(addDot(classNames.tagWrapper));
            this.baseHeight = this.$tagWrapper.height();

            // Get the input
            this.$inputTagField = this.$tagWrapper.find(addDot(classNames.selectTextarea));

            // Hide the popover when blurring the inputTagField
            this.$inputTagField.on(constants.blur, function () {
                $this.$popover.hide();
            });

            // Get the tags in the wrapper
            this.$tags = this.$tagWrapper.find(addDot(classNames.selectTags));

            // Show Popover on click of tags
            this.$tags
                .on(constants.click, this.initializePopover.bind(this));

            // Also Attach to placeHolder Text
            this.$tags.next(addDot(classNames.placeholderText))
                .on(constants.click, this.initializePopover.bind(this));

            // Attach Event Listener to ul list
            this.$tags.on(constants.click, addDot(classNames.popoverClose), function () {
                $this.inputToPopover($(this));
            });

            // Attach List Event Handlers to Li
            this.$popover.find(addDot(classNames.selectList)).on(constants.mousedown, function (e) {
                e.preventDefault();
            }).on(constants.click, constants.li, function () {
                $this.popoverToInput($(this));
            });

            // Finally Hide the Element
            this.$elem.hide();

            // Required for placeholdertext and pre-selected values
            this.checkNumberOfTags();

            // If pre-selected are higher than normal
            this.changeSize();

            // Trigger init event
            this.$elem.trigger('popselect:init');

            if (this.settings.autofocus) {
                this.initializePopover();
            }
        },
        inputToPopover: function ($elem) {
            var $li = $elem.parent();
            this.log(logs.closeClicked, $li);
            var val = $li.attr(constants.attrVal);
            var text = $li.attr(constants.attrText);

            // Remove them from input and add it to popover
            this.appendToPopup(val, text);
            $li.remove();

            // Standard Reset Calls
            this.setPlaceholder();
            this.focus();

            // Whether to increase/decrease width
            this.changeSize();

            // Whether to enable / disable popover and Placeholder Text
            this.checkNumberOfTags();

            // Trigger remove event, passing value and text of removed tag
            this.$elem.trigger('popselect:remove', [val, text]);
        },
        enablePopover: function () {
            this.$popover.find(addDot(classNames.selectList) + ' li')
                .removeClass(classNames.popoverDisabled);
        },
        disablePopover: function () {
            this.$popover.find(addDot(classNames.selectList) + ' li')
                .addClass(classNames.popoverDisabled);
        },
        checkNumberOfTags: function () {
            var currentNo = this.$tags.find(addDot(classNames.tag)).length;
            if (currentNo === 0) {
                this.enablePlaceHolderText();
            } else {
                this.disablePlaceHolderText();
            }

            if (this.settings.maxAllowed !== 0) {
                if (this.settings.maxAllowed > currentNo) {
                    this.enablePopover();
                } else {
                    this.disablePopover();
                }
            }

            this.syncWithSelect();
        },
        popoverToInput: function ($elem) {
            var val = $elem.attr(constants.attrVal);
            var text = $elem.text();
            var li = createTagStr(val, text);

            // Remove them from popover and it to input
            this.$tags.append(li);
            $elem.remove();

            // Standard Reset Calls
            this.setPlaceholder();
            this.focus();
            this.popoverShow();
            this.changePosition();

            // Whether to increase/decrease width
            this.changeSize();

            // Enable / Disable Popover
            this.checkNumberOfTags();

            // Trigger add event, passing value and text of added tag
            this.$elem.trigger('popselect:add', [val, text]);
        },
        popoverShow: function () {
            // Change Position as well show popover
            if (this.$popover.find(addDot(classNames.selectList) + ' li').length) {
                this.$popover.show();
            } else {
                this.$popover.hide();
            }
        },
        initializePopover: function () {
            this.popoverShow();
            this.changePosition();
            this.setPlaceholder();
            this.focus();
        },
        enablePlaceHolderText: function () {
            this.$tags.next(addDot(classNames.placeholderText)).show();
        },
        disablePlaceHolderText: function () {
            this.$tags.next(addDot(classNames.placeholderText)).hide();
        },
        focus: function () {
            var $this = this;
            this.$tags.find(addDot(classNames.placeholderInput)).focus();
            this.$tags.find(addDot(classNames.placeholderInput)).on(constants.blur, function () {
                $this.$popover.hide();
            });
        },
        setPlaceholder: function () {
            if (this.$tags.children(addDot(classNames.placeholder)).length) {
                this.$tags.children(addDot(classNames.placeholder)).remove();
            }
            this.$tags.append(createPlaceholderInput());
            this.disableInput();
        },
        disableInput: function () {
            var $this = this;
            this.$tags.find(addDot(classNames.placeholderInput)).keyup(function (e) {
                // Empty the input always
                $(this).val('');

                // For delete key, backspace and Ctrl + x Key
                if (e.which === 8 || e.which === 46 || e.ctrlKey && e.which === 88) {
                    $this.removeLastElem();
                }
            });
        },
        changeSize: function () {
            if (this.settings.autoIncrease) {
                var tagWidth = 0;
                var textWidth = this.settings.width || this.elemPos.width;
                this.$tags.find(addDot(classNames.tag)).each(function (i, elem) {
                    tagWidth += $(elem).outerWidth() + 20;
                });
                var mHeight = Math.floor(tagWidth / textWidth);
                this.$tags.height((mHeight + 1) * this.baseHeight);
            }
        },
        removeLastElem: function () {
            // Delete the last selected li if present
            var tags = this.$tags.find(addDot(classNames.tag));
            if (tags.length) {
                var $li = $(tags[tags.length - 1]);
                var val = $li.attr(constants.attrVal);
                var text = $li.attr(constants.attrText);

                // Remove them from input and add it to popover
                this.appendToPopup(val, text);
                $li.remove();

                // Standard Reset Calls
                this.changePosition();
                this.setPlaceholder();
                this.focus();

                // Whether to increase/decrease width
                this.changeSize();

                // Enable / Disable Popover
                this.checkNumberOfTags();
            } else {
                this.log(logs.noElem);
            }
        },
        setTitle: function (title) {
            if (this.settings.showTitle) {
                this.$popover.find(addDot(classNames.selectTitle)).text(title);
            }
        },
        getPosition: function ($element) {
            $element = $element || this.$element;
            var el = $element[0];
            var isBody = el.tagName === constants.body;

            var elRect = el.getBoundingClientRect();
            if (elRect.width == null) {
                var w = elRect.right - elRect.left;
                var h = elRect.bottom - elRect.top;
                elRect = $.extend({}, elRect, {width: w, height: h});
            }
            var elOffset = isBody ? {top: 0, left: 0} : $element.offset();
            /* jshint ignore:start */
            var scroll = {scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop()}
            /* jshint ignore:end */
            var outerDims = isBody ? {width: $(window).width(), height: $(window).height()} : null;

            return $.extend({}, elRect, scroll, outerDims, elOffset);
        },
        syncWithSelect: function () {
            var arrValues = this.$tags.find(addDot(classNames.tag)).map(function (i, elem) {
                return $(elem).attr('data-value');
            }).toArray();
            this.$elem.children(constants.option).each(function (i, option) {
                if (arrValues.indexOf($(option).val()) < 0) {
                    $(option).removeAttr('selected');
                } else {
                    $(option).attr('selected', 'selected');
                }
            });
        },
        appendToPopup: function (val, text) {
            var li = createLiTag(val, text);
            this.$popover.find(addDot(classNames.selectList)).append(li);
        },
        generatePopover: function (options) {
            var list = '';
            for (var i = 0; i < options.length; i++) {
                if (!options[i].selected) {
                    list += createLiTag(options[i].val, options[i].text);
                }
            }
            var popoverStr = createPopoverStr(list, this.settings);
            return popoverStr;
        },
        changePosition: function () {
            // It first needs to be placed
            var popPos = this.getPosition(this.$popover);
            var tagPos = this.getPosition(this.$tagWrapper);

            var leftOffset = ((this.settings.width || this.elemPos.width) / 2) - (popPos.width / 2);
            var topOffset;
            if (this.settings.position === 'top') {
                topOffset = -(popPos.height);
            } else {
                topOffset = tagPos.height;
            }

            this.log('popPos.width', popPos.width);
            this.log(logs.posChanged, topOffset, leftOffset);
            this.$popover.css({top: topOffset, left: leftOffset});
        },
        log: function () {
            if (this.settings.debug) {
                console.log.apply(console, arguments);
            }
        }
    });

    /**
     * A quick helper function for creating templates
     * @param  {string} s Template String
     * @param  {object} d Values to replace for
     * @return {string}   Populated template string
     */
    function template(s, d) {
        for (var p in d) {
            s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]);
        }
        return s;
    }

    /**
     * Just adds a dot for easy class selection
     * @param {string} str DOM className
     * @return {string} jQuery selector
     */
    function addDot(str) {
        return '.' + str;
    }

    function createEmptyDiv(x) {
        if (x) {
            return '<div class="{' + x + '}"></div>';
        } else {
            return '<div class="{wrapper}"></div>';
        }
    }

    function createTagsLi(options) {
        var str = '';
        for (var i = 0; i < options.length; i++) {
            if (options[i].selected) {
                str += createTagStr(options[i].val, options[i].text);
            }
        }
        return str;
    }

    function createTaggingStr(text, options) {
        return template('<div class="{tagWrapper}">' +
            '<textarea class="{selectTextarea}"></textarea>' +
            '<ul class="{selectTags}">' +
            '{tags}' +
            '</ul>' +
            '<div class="{placeholderText}">' +
            '{text}' +
            '</div>' +
            '</div>', {
            tags: createTagsLi(options),
            text: text,
            placeholderText: classNames.placeholderText,
            tagWrapper: classNames.tagWrapper,
            selectTextarea: classNames.selectTextarea,
            selectTags: classNames.selectTags
        });
    }

    function createTagStr(val, text) {
        return template('<li class="{tag}" data-value="{val}" data-text="{text}">' +
            '<span class="{popoverClose}">&times;</span>{text}' +
            '</li>', {
            text: text,
            val: val,
            tag: classNames.tag,
            popoverClose: classNames.popoverClose
        });
    }

    function createLiTag(val, text) {
        return template('<li data-value="{val}" data-text="{text}">{text}</li>', {
            val: val,
            text: text
        });
    }

    function createPlaceholderInput() {
        return template('<li class="{placeholder}">' +
            '<div>' +
            '<input type="text" readonly="true">' +
            '</div>' +
            '</li>', {
            placeholder: classNames.placeholder
        });
    }

    function createPopoverStr(list, settings) {
        return template('<div class="{popoverSelect} {top}">' +
            (settings.showTitle ? '<h3 class="{selectTitle}">{title}</h3>' : '') +
            '<div class="{popoverBody}">' +
            '<ul class="{selectList}">' +
            '{list}' +
            '</ul>' +
            '</div>' +
            '<div class="{arrow}"></div>' +
            '</div>', {
            title: settings.title,
            list: list,
            arrow: classNames.arrow,
            popoverSelect: classNames.popoverSelect,
            popoverBody: classNames.popoverBody,
            selectList: classNames.selectList,
            top: settings.position,
            selectTitle: classNames.selectTitle
        });
    }

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn.popSelect = function (options) {
        if (typeof (options) === 'string') {
            if (options === 'value') {
                return this.next(addDot(classNames.tagWrapper))
                    .find(addDot(classNames.tag)).map(function (i, $elem) {
                        return $($elem).attr(constants.attrVal);
                    });
            } else {
                console.warn(logs.unSupported);
            }
        } else {
            return this.each(function () {
                if (!$.data(this, 'plugin_' + pluginName)) {
                    $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
                }
            });
        }
    };

})(jQuery, window, document);
